@@ -63,6 +63,9 @@ gem 'haversine' |
||
63 | 63 |
gem 'omniauth-evernote' |
64 | 64 |
gem 'evernote_oauth' |
65 | 65 |
|
66 |
+# LocalFileAgent (watch functionality) |
|
67 |
+gem 'listen', '~> 3.0.5', require: false |
|
68 |
+ |
|
66 | 69 |
# Optional Services. |
67 | 70 |
gem 'omniauth-37signals' # BasecampAgent |
68 | 71 |
gem 'omniauth-wunderlist', github: 'wunderlist/omniauth-wunderlist', ref: 'd0910d0396107b9302aa1bc50e74bb140990ccb8' |
@@ -75,7 +78,6 @@ unless Gem::Version.new(Bundler::VERSION) >= Gem::Version.new('1.5.0') |
||
75 | 78 |
end |
76 | 79 |
|
77 | 80 |
gem 'protected_attributes', '~>1.0.8' # This must be loaded before some other gems, like delayed_job. |
78 |
- |
|
79 | 81 |
gem 'ace-rails-ap', '~> 2.0.1' |
80 | 82 |
gem 'bootstrap-kaminari-views', '~> 0.0.3' |
81 | 83 |
gem 'bundler', '>= 1.5.0' |
@@ -621,6 +621,7 @@ DEPENDENCIES |
||
621 | 621 |
kramdown (~> 1.3.3) |
622 | 622 |
letter_opener_web |
623 | 623 |
liquid (~> 3.0.3) |
624 |
+ listen (~> 3.0.5) |
|
624 | 625 |
mini_magick |
625 | 626 |
mqtt |
626 | 627 |
multi_xml |
@@ -0,0 +1,58 @@ |
||
1 |
+module FileHandling |
|
2 |
+ extend ActiveSupport::Concern |
|
3 |
+ |
|
4 |
+ def get_file_pointer(file) |
|
5 |
+ { file_pointer: { file: file, agent_id: id } } |
|
6 |
+ end |
|
7 |
+ |
|
8 |
+ def get_io(event) |
|
9 |
+ return nil unless event.payload['file_pointer'] && |
|
10 |
+ event.payload['file_pointer']['file'] && |
|
11 |
+ event.payload['file_pointer']['agent_id'] |
|
12 |
+ event.user.agents.find(event.payload['file_pointer']['agent_id']).get_io(event.payload['file_pointer']['file']) |
|
13 |
+ end |
|
14 |
+ |
|
15 |
+ def emitting_file_handling_agent_description |
|
16 |
+ @emitting_file_handling_agent_description ||= |
|
17 |
+ "This agent only emits a 'file pointer', not the data inside the files, the following agents can consume the created events: `#{receiving_file_handling_agents.join('`, `')}`. Read more about the concept in the [wiki](https://github.com/cantino/huginn/wiki/How-Huginn-works-with-files)." |
|
18 |
+ end |
|
19 |
+ |
|
20 |
+ def receiving_file_handling_agent_description |
|
21 |
+ @receiving_file_handling_agent_description ||= |
|
22 |
+ "This agent can consume a 'file pointer' event from the following agents with no additional configuration: `#{emitting_file_handling_agents.join('`, `')}`. Read more about the concept in the [wiki](https://github.com/cantino/huginn/wiki/How-Huginn-works-with-files)." |
|
23 |
+ end |
|
24 |
+ |
|
25 |
+ private |
|
26 |
+ |
|
27 |
+ def emitting_file_handling_agents |
|
28 |
+ emitting_file_handling_agents = file_handling_agents.select { |a| a.emits_file_pointer? } |
|
29 |
+ emitting_file_handling_agents.map { |a| a.to_s.demodulize } |
|
30 |
+ end |
|
31 |
+ |
|
32 |
+ def receiving_file_handling_agents |
|
33 |
+ receiving_file_handling_agents = file_handling_agents.select { |a| a.consumes_file_pointer? } |
|
34 |
+ receiving_file_handling_agents.map { |a| a.to_s.demodulize } |
|
35 |
+ end |
|
36 |
+ |
|
37 |
+ def file_handling_agents |
|
38 |
+ @file_handling_agents ||= Agent.types.select{ |c| c.included_modules.include?(FileHandling) }.map { |d| d.name.constantize } |
|
39 |
+ end |
|
40 |
+ |
|
41 |
+ module ClassMethods |
|
42 |
+ def emits_file_pointer! |
|
43 |
+ @emits_file_pointer = true |
|
44 |
+ end |
|
45 |
+ |
|
46 |
+ def emits_file_pointer? |
|
47 |
+ !!@emits_file_pointer |
|
48 |
+ end |
|
49 |
+ |
|
50 |
+ def consumes_file_pointer! |
|
51 |
+ @consumes_file_pointer = true |
|
52 |
+ end |
|
53 |
+ |
|
54 |
+ def consumes_file_pointer? |
|
55 |
+ !!@consumes_file_pointer |
|
56 |
+ end |
|
57 |
+ end |
|
58 |
+end |
@@ -12,4 +12,8 @@ module WorkingHelpers |
||
12 | 12 |
def received_event_without_error? |
13 | 13 |
(last_receive_at.present? && last_error_log_at.blank?) || (last_receive_at.present? && last_error_log_at.present? && last_receive_at > last_error_log_at) |
14 | 14 |
end |
15 |
-end |
|
15 |
+ |
|
16 |
+ def checked_without_error? |
|
17 |
+ (last_check_at.present? && last_error_log_at.nil?) || (last_check_at.present? && last_error_log_at.present? && last_check_at > last_error_log_at) |
|
18 |
+ end |
|
19 |
+end |
@@ -0,0 +1,190 @@ |
||
1 |
+module Agents |
|
2 |
+ class LocalFileAgent < Agent |
|
3 |
+ include LongRunnable |
|
4 |
+ include FormConfigurable |
|
5 |
+ include FileHandling |
|
6 |
+ |
|
7 |
+ emits_file_pointer! |
|
8 |
+ |
|
9 |
+ default_schedule 'every_1h' |
|
10 |
+ |
|
11 |
+ def self.should_run? |
|
12 |
+ ENV['ENABLE_INSECURE_AGENTS'] == "true" |
|
13 |
+ end |
|
14 |
+ |
|
15 |
+ description do |
|
16 |
+ <<-MD |
|
17 |
+ The LocalFileAgent can watch a file/directory for changes or emit an event for every file in that directory. When receiving an event it writes the received data into a file. |
|
18 |
+ |
|
19 |
+ `mode` determines if the agent is emitting events for (changed) files or writing received event data to disk. |
|
20 |
+ |
|
21 |
+ ### Reading |
|
22 |
+ |
|
23 |
+ When `watch` is set to `true` the LocalFileAgent will watch the specified `path` for changes, the schedule is ignored and the file system is watched continuously. An event will be emitted for every detected change. |
|
24 |
+ |
|
25 |
+ When `watch` is set to `false` the agent will emit an event for every file in the directory on each scheduled run. |
|
26 |
+ |
|
27 |
+ #{emitting_file_handling_agent_description} |
|
28 |
+ |
|
29 |
+ ### Writing |
|
30 |
+ |
|
31 |
+ Every event will be writting into a file at `path`, Liquid interpolation is possible to change the path per event. |
|
32 |
+ |
|
33 |
+ When `append` is true the received data will be appended to the file. |
|
34 |
+ |
|
35 |
+ Use [Liquid](https://github.com/cantino/huginn/wiki/Formatting-Events-using-Liquid) templating in `data` to specify which part of the received event should be written. |
|
36 |
+ |
|
37 |
+ *Warning*: This type of Agent can read and write any file the user that runs the Huginn server has access to, and is #{Agents::LocalFileAgent.should_run? ? "**currently enabled**" : "**currently disabled**"}. |
|
38 |
+ Only enable this Agent if you trust everyone using your Huginn installation. |
|
39 |
+ You can enable this Agent in your .env file by setting `ENABLE_INSECURE_AGENTS` to `true`. |
|
40 |
+ MD |
|
41 |
+ end |
|
42 |
+ |
|
43 |
+ event_description do |
|
44 |
+ "Events will looks like this:\n\n %s" % if boolify(interpolated['watch']) |
|
45 |
+ Utils.pretty_print( |
|
46 |
+ "file_pointer" => { |
|
47 |
+ "file" => "/tmp/test/filename", |
|
48 |
+ "agent_id" => id |
|
49 |
+ }, |
|
50 |
+ "event_type" => "modified/added/removed" |
|
51 |
+ ) |
|
52 |
+ else |
|
53 |
+ Utils.pretty_print( |
|
54 |
+ "file_pointer" => { |
|
55 |
+ "file" => "/tmp/test/filename", |
|
56 |
+ "agent_id" => id |
|
57 |
+ } |
|
58 |
+ ) |
|
59 |
+ end |
|
60 |
+ end |
|
61 |
+ |
|
62 |
+ def default_options |
|
63 |
+ { |
|
64 |
+ 'mode' => 'read', |
|
65 |
+ 'watch' => 'true', |
|
66 |
+ 'append' => 'false', |
|
67 |
+ 'path' => "", |
|
68 |
+ 'data' => '{{ data }}' |
|
69 |
+ } |
|
70 |
+ end |
|
71 |
+ |
|
72 |
+ form_configurable :mode, type: :array, values: %w(read write) |
|
73 |
+ form_configurable :watch, type: :array, values: %w(true false) |
|
74 |
+ form_configurable :path, type: :string |
|
75 |
+ form_configurable :append, type: :boolean |
|
76 |
+ form_configurable :data, type: :string |
|
77 |
+ |
|
78 |
+ def validate_options |
|
79 |
+ if options['mode'].blank? || !['read', 'write'].include?(options['mode']) |
|
80 |
+ errors.add(:base, "The 'mode' option is required and must be set to 'read' or 'write'") |
|
81 |
+ end |
|
82 |
+ if options['watch'].blank? || ![true, false].include?(boolify(options['watch'])) |
|
83 |
+ errors.add(:base, "The 'watch' option is required and must be set to 'true' or 'false'") |
|
84 |
+ end |
|
85 |
+ if options['append'].blank? || ![true, false].include?(boolify(options['append'])) |
|
86 |
+ errors.add(:base, "The 'append' option is required and must be set to 'true' or 'false'") |
|
87 |
+ end |
|
88 |
+ if options['path'].blank? |
|
89 |
+ errors.add(:base, "The 'path' option is required.") |
|
90 |
+ end |
|
91 |
+ end |
|
92 |
+ |
|
93 |
+ def working? |
|
94 |
+ should_run?(false) && ((interpolated['mode'] == 'read' && check_path_existance && checked_without_error?) || |
|
95 |
+ (interpolated['mode'] == 'write' && received_event_without_error?)) |
|
96 |
+ end |
|
97 |
+ |
|
98 |
+ def check |
|
99 |
+ return if interpolated['mode'] != 'read' || boolify(interpolated['watch']) || !should_run? |
|
100 |
+ return unless check_path_existance(true) |
|
101 |
+ if File.directory?(expanded_path) |
|
102 |
+ Dir.glob(File.join(expanded_path, '*')).select { |f| File.file?(f) } |
|
103 |
+ else |
|
104 |
+ [expanded_path] |
|
105 |
+ end.each do |file| |
|
106 |
+ create_event payload: get_file_pointer(file) |
|
107 |
+ end |
|
108 |
+ end |
|
109 |
+ |
|
110 |
+ def receive(incoming_events) |
|
111 |
+ return if interpolated['mode'] != 'write' || !should_run? |
|
112 |
+ incoming_events.each do |event| |
|
113 |
+ mo = interpolated(event) |
|
114 |
+ File.open(File.expand_path(mo['path']), boolify(mo['append']) ? 'a' : 'w') do |file| |
|
115 |
+ file.write(mo['data']) |
|
116 |
+ end |
|
117 |
+ end |
|
118 |
+ end |
|
119 |
+ |
|
120 |
+ def start_worker? |
|
121 |
+ interpolated['mode'] == 'read' && boolify(interpolated['watch']) && should_run? && check_path_existance |
|
122 |
+ end |
|
123 |
+ |
|
124 |
+ def check_path_existance(log = true) |
|
125 |
+ if !File.exist?(expanded_path) |
|
126 |
+ error("File or directory '#{expanded_path}' does not exist") if log |
|
127 |
+ return false |
|
128 |
+ end |
|
129 |
+ true |
|
130 |
+ end |
|
131 |
+ |
|
132 |
+ def get_io(file) |
|
133 |
+ File.open(file, 'r') |
|
134 |
+ end |
|
135 |
+ |
|
136 |
+ def expanded_path |
|
137 |
+ @expanded_path ||= File.expand_path(interpolated['path']) |
|
138 |
+ end |
|
139 |
+ |
|
140 |
+ private |
|
141 |
+ |
|
142 |
+ def should_run?(log = true) |
|
143 |
+ if self.class.should_run? |
|
144 |
+ true |
|
145 |
+ else |
|
146 |
+ error("Unable to run because insecure agents are not enabled. Set ENABLE_INSECURE_AGENTS to true in the Huginn .env configuration.") if log |
|
147 |
+ false |
|
148 |
+ end |
|
149 |
+ end |
|
150 |
+ |
|
151 |
+ class Worker < LongRunnable::Worker |
|
152 |
+ def setup |
|
153 |
+ require 'listen' |
|
154 |
+ @listener = Listen.to(*listen_options, &method(:callback)) |
|
155 |
+ end |
|
156 |
+ |
|
157 |
+ def run |
|
158 |
+ sleep unless agent.check_path_existance(true) |
|
159 |
+ |
|
160 |
+ @listener.start |
|
161 |
+ sleep |
|
162 |
+ end |
|
163 |
+ |
|
164 |
+ def stop |
|
165 |
+ @listener.stop |
|
166 |
+ end |
|
167 |
+ |
|
168 |
+ private |
|
169 |
+ |
|
170 |
+ def callback(*changes) |
|
171 |
+ AgentRunner.with_connection do |
|
172 |
+ changes.zip([:modified, :added, :removed]).each do |files, event_type| |
|
173 |
+ files.each do |file| |
|
174 |
+ agent.create_event payload: agent.get_file_pointer(file).merge(event_type: event_type) |
|
175 |
+ end |
|
176 |
+ end |
|
177 |
+ agent.touch(:last_check_at) |
|
178 |
+ end |
|
179 |
+ end |
|
180 |
+ |
|
181 |
+ def listen_options |
|
182 |
+ if File.directory?(agent.expanded_path) |
|
183 |
+ [agent.expanded_path, ignore!: [] ] |
|
184 |
+ else |
|
185 |
+ [File.dirname(agent.expanded_path), { ignore!: [], only: /\A#{Regexp.escape(File.basename(agent.expanded_path))}\z/ } ] |
|
186 |
+ end |
|
187 |
+ end |
|
188 |
+ end |
|
189 |
+ end |
|
190 |
+end |
@@ -118,5 +118,6 @@ end |
||
118 | 118 |
|
119 | 119 |
require 'agents/twitter_stream_agent' |
120 | 120 |
require 'agents/jabber_agent' |
121 |
+require 'agents/local_file_agent' |
|
121 | 122 |
require 'huginn_scheduler' |
122 | 123 |
require 'delayed_job_worker' |
@@ -15,7 +15,7 @@ module Utils |
||
15 | 15 |
def self.pretty_print(struct, indent = true) |
16 | 16 |
output = JSON.pretty_generate(struct) |
17 | 17 |
if indent |
18 |
- output.gsub(/\n/i, "\n ").tap { |a| p a } |
|
18 |
+ output.gsub(/\n/i, "\n ") |
|
19 | 19 |
else |
20 | 20 |
output |
21 | 21 |
end |
@@ -12,3 +12,4 @@ EVERNOTE_OAUTH_KEY=evernoteoauthkey |
||
12 | 12 |
EVERNOTE_OAUTH_SECRET=evernoteoauthsecret |
13 | 13 |
FAILED_JOBS_TO_KEEP=2 |
14 | 14 |
REQUIRE_CONFIRMED_EMAIL=false |
15 |
+ENABLE_INSECURE_AGENTS=true |
@@ -0,0 +1,276 @@ |
||
1 |
+require 'rails_helper' |
|
2 |
+ |
|
3 |
+describe Agents::LocalFileAgent do |
|
4 |
+ before(:each) do |
|
5 |
+ @valid_params = { |
|
6 |
+ 'mode' => 'read', |
|
7 |
+ 'watch' => 'false', |
|
8 |
+ 'append' => 'false', |
|
9 |
+ 'path' => File.join(Rails.root, 'tmp', 'spec') |
|
10 |
+ } |
|
11 |
+ FileUtils.mkdir_p File.join(Rails.root, 'tmp', 'spec') |
|
12 |
+ |
|
13 |
+ @checker = Agents::LocalFileAgent.new(:name => "somename", :options => @valid_params) |
|
14 |
+ @checker.user = users(:jane) |
|
15 |
+ @checker.save! |
|
16 |
+ end |
|
17 |
+ |
|
18 |
+ after(:all) do |
|
19 |
+ FileUtils.rm_r File.join(Rails.root, 'tmp', 'spec') |
|
20 |
+ end |
|
21 |
+ |
|
22 |
+ describe "#validate_options" do |
|
23 |
+ it "is valid with the given options" do |
|
24 |
+ expect(@checker).to be_valid |
|
25 |
+ end |
|
26 |
+ |
|
27 |
+ it "requires mode to be either 'read' or 'write'" do |
|
28 |
+ @checker.options['mode'] = 'write' |
|
29 |
+ expect(@checker).to be_valid |
|
30 |
+ @checker.options['mode'] = 'write' |
|
31 |
+ expect(@checker).to be_valid |
|
32 |
+ @checker.options['mode'] = 'test' |
|
33 |
+ expect(@checker).not_to be_valid |
|
34 |
+ end |
|
35 |
+ |
|
36 |
+ it "requires the path to be set" do |
|
37 |
+ @checker.options['path'] = '' |
|
38 |
+ expect(@checker).not_to be_valid |
|
39 |
+ end |
|
40 |
+ |
|
41 |
+ it "requires watch to be present" do |
|
42 |
+ @checker.options['watch'] = '' |
|
43 |
+ expect(@checker).not_to be_valid |
|
44 |
+ end |
|
45 |
+ |
|
46 |
+ it "requires watch to be either 'true' or 'false'" do |
|
47 |
+ @checker.options['watch'] = 'true' |
|
48 |
+ expect(@checker).to be_valid |
|
49 |
+ @checker.options['watch'] = 'false' |
|
50 |
+ expect(@checker).to be_valid |
|
51 |
+ @checker.options['watch'] = 'test' |
|
52 |
+ expect(@checker).not_to be_valid |
|
53 |
+ end |
|
54 |
+ |
|
55 |
+ it "requires append to be either 'true' or 'false'" do |
|
56 |
+ @checker.options['append'] = 'true' |
|
57 |
+ expect(@checker).to be_valid |
|
58 |
+ @checker.options['append'] = 'false' |
|
59 |
+ expect(@checker).to be_valid |
|
60 |
+ @checker.options['append'] = 'test' |
|
61 |
+ expect(@checker).not_to be_valid |
|
62 |
+ end |
|
63 |
+ end |
|
64 |
+ |
|
65 |
+ context "#working" do |
|
66 |
+ it "is working with no recent errors in read mode" do |
|
67 |
+ @checker.last_check_at = Time.now |
|
68 |
+ expect(@checker).to be_working |
|
69 |
+ end |
|
70 |
+ |
|
71 |
+ it "is working with no recent errors in write mode" do |
|
72 |
+ @checker.options['mode'] = 'write' |
|
73 |
+ @checker.last_receive_at = Time.now |
|
74 |
+ expect(@checker).to be_working |
|
75 |
+ end |
|
76 |
+ end |
|
77 |
+ |
|
78 |
+ context "#check_path_existance" do |
|
79 |
+ it "is truethy when the path exists" do |
|
80 |
+ expect(@checker.check_path_existance).to be_truthy |
|
81 |
+ end |
|
82 |
+ |
|
83 |
+ it "is falsy when the path does not exist" do |
|
84 |
+ @checker.options['path'] = '/doesnotexist' |
|
85 |
+ expect(@checker.check_path_existance).to be_falsy |
|
86 |
+ end |
|
87 |
+ |
|
88 |
+ it "create a log entry" do |
|
89 |
+ @checker.options['path'] = '/doesnotexist' |
|
90 |
+ expect { @checker.check_path_existance(true) }.to change(AgentLog, :count).by(1) |
|
91 |
+ end |
|
92 |
+ |
|
93 |
+ it "works with non-expanded paths" do |
|
94 |
+ @checker.options['path'] = '~' |
|
95 |
+ expect(@checker.check_path_existance).to be_truthy |
|
96 |
+ end |
|
97 |
+ end |
|
98 |
+ |
|
99 |
+ def with_files(*files) |
|
100 |
+ files.each { |f| FileUtils.touch(f) } |
|
101 |
+ yield |
|
102 |
+ files.each { |f| FileUtils.rm(f) } |
|
103 |
+ end |
|
104 |
+ |
|
105 |
+ context "#check" do |
|
106 |
+ it "does not create events when the directory is empty" do |
|
107 |
+ expect { @checker.check }.to change(Event, :count).by(0) |
|
108 |
+ end |
|
109 |
+ |
|
110 |
+ it "creates an event for every file in the directory" do |
|
111 |
+ with_files(File.join(Rails.root, 'tmp', 'spec', 'one'), File.join(Rails.root, 'tmp', 'spec', 'two')) do |
|
112 |
+ expect { @checker.check }.to change(Event, :count).by(2) |
|
113 |
+ expect(Event.last.payload.has_key?('file_pointer')).to be_truthy |
|
114 |
+ end |
|
115 |
+ end |
|
116 |
+ |
|
117 |
+ it "creates an event if the configured file exists" do |
|
118 |
+ @checker.options['path'] = File.join(Rails.root, 'tmp', 'spec', 'one') |
|
119 |
+ with_files(File.join(Rails.root, 'tmp', 'spec', 'one'), File.join(Rails.root, 'tmp', 'spec', 'two')) do |
|
120 |
+ expect { @checker.check }.to change(Event, :count).by(1) |
|
121 |
+ payload = Event.last.payload |
|
122 |
+ expect(payload.has_key?('file_pointer')).to be_truthy |
|
123 |
+ expect(payload['file_pointer']['file']).to eq(@checker.options['path']) |
|
124 |
+ end |
|
125 |
+ end |
|
126 |
+ |
|
127 |
+ it "does not run when ENABLE_INSECURE_AGENTS is not set to true" do |
|
128 |
+ ENV['ENABLE_INSECURE_AGENTS'] = 'false' |
|
129 |
+ expect { @checker.check }.to change(AgentLog, :count).by(1) |
|
130 |
+ ENV['ENABLE_INSECURE_AGENTS'] = 'true' |
|
131 |
+ end |
|
132 |
+ end |
|
133 |
+ |
|
134 |
+ context "#event_description" do |
|
135 |
+ it "should include event_type when watch is set to true" do |
|
136 |
+ @checker.options['watch'] = 'true' |
|
137 |
+ expect(@checker.event_description).to include('event_type') |
|
138 |
+ end |
|
139 |
+ |
|
140 |
+ it "should not include event_type when watch is set to false" do |
|
141 |
+ @checker.options['watch'] = 'false' |
|
142 |
+ expect(@checker.event_description).not_to include('event_type') |
|
143 |
+ end |
|
144 |
+ end |
|
145 |
+ |
|
146 |
+ it "get_io opens the file" do |
|
147 |
+ mock(File).open('test', 'r') |
|
148 |
+ @checker.get_io('test') |
|
149 |
+ end |
|
150 |
+ |
|
151 |
+ context "#start_worker?" do |
|
152 |
+ it "reeturns true when watch is true" do |
|
153 |
+ @checker.options['watch'] = 'true' |
|
154 |
+ expect(@checker.start_worker?).to be_truthy |
|
155 |
+ end |
|
156 |
+ |
|
157 |
+ it "returns false when watch is false" do |
|
158 |
+ @checker.options['watch'] = 'false' |
|
159 |
+ expect(@checker.start_worker?).to be_falsy |
|
160 |
+ end |
|
161 |
+ end |
|
162 |
+ |
|
163 |
+ context "#receive" do |
|
164 |
+ before(:each) do |
|
165 |
+ @checker.options['mode'] = 'write' |
|
166 |
+ @checker.options['data'] = '{{ data }}' |
|
167 |
+ @file_mock = mock() |
|
168 |
+ end |
|
169 |
+ |
|
170 |
+ it "writes the data at data into a file" do |
|
171 |
+ mock(@file_mock).write('hello world') |
|
172 |
+ event = Event.new(payload: {'data' => 'hello world'}) |
|
173 |
+ mock(File).open(File.join(Rails.root, 'tmp', 'spec'), 'w').yields @file_mock |
|
174 |
+ @checker.receive([event]) |
|
175 |
+ end |
|
176 |
+ |
|
177 |
+ it "appends the data at data onto a file" do |
|
178 |
+ mock(@file_mock).write('hello world') |
|
179 |
+ @checker.options['append'] = 'true' |
|
180 |
+ event = Event.new(payload: {'data' => 'hello world'}) |
|
181 |
+ mock(File).open(File.join(Rails.root, 'tmp', 'spec'), 'a').yields @file_mock |
|
182 |
+ @checker.receive([event]) |
|
183 |
+ end |
|
184 |
+ |
|
185 |
+ it "does not receive when ENABLE_INSECURE_AGENTS is not set to true" do |
|
186 |
+ ENV['ENABLE_INSECURE_AGENTS'] = 'false' |
|
187 |
+ expect { @checker.receive([]) }.to change(AgentLog, :count).by(1) |
|
188 |
+ ENV['ENABLE_INSECURE_AGENTS'] = 'true' |
|
189 |
+ end |
|
190 |
+ end |
|
191 |
+ |
|
192 |
+ describe describe Agents::LocalFileAgent::Worker do |
|
193 |
+ require 'listen' |
|
194 |
+ |
|
195 |
+ before(:each) do |
|
196 |
+ @checker.options['watch'] = true |
|
197 |
+ @checker.save |
|
198 |
+ @worker = Agents::LocalFileAgent::Worker.new(agent: @checker) |
|
199 |
+ @listen_mock = mock() |
|
200 |
+ end |
|
201 |
+ |
|
202 |
+ context "#setup" do |
|
203 |
+ it "initializes the listen gem" do |
|
204 |
+ mock(Listen).to(@checker.options['path'], ignore!: []) |
|
205 |
+ @worker.setup |
|
206 |
+ end |
|
207 |
+ end |
|
208 |
+ |
|
209 |
+ context "#run" do |
|
210 |
+ before(:each) do |
|
211 |
+ stub(Listen).to { @listen_mock } |
|
212 |
+ @worker.setup |
|
213 |
+ end |
|
214 |
+ |
|
215 |
+ it "starts to listen to changes in the directory when the path is present" do |
|
216 |
+ mock(@worker).sleep |
|
217 |
+ mock(@listen_mock).start |
|
218 |
+ @worker.run |
|
219 |
+ end |
|
220 |
+ |
|
221 |
+ it "does nothing when the path does not exist" do |
|
222 |
+ mock(@worker.agent).check_path_existance(true) { false } |
|
223 |
+ dont_allow(@listen_mock).start |
|
224 |
+ mock(@worker).sleep { raise "Sleeping" } |
|
225 |
+ expect { @worker.run }.to raise_exception(RuntimeError, 'Sleeping') |
|
226 |
+ end |
|
227 |
+ end |
|
228 |
+ |
|
229 |
+ context "#stop" do |
|
230 |
+ it "stops the listen gem" do |
|
231 |
+ stub(Listen).to { @listen_mock } |
|
232 |
+ @worker.setup |
|
233 |
+ mock(@listen_mock).stop |
|
234 |
+ @worker.stop |
|
235 |
+ end |
|
236 |
+ end |
|
237 |
+ |
|
238 |
+ context "#callback" do |
|
239 |
+ let(:file) { File.join(Rails.root, 'tmp', 'one') } |
|
240 |
+ let(:file2) { File.join(Rails.root, 'tmp', 'one2') } |
|
241 |
+ |
|
242 |
+ it "creates an event for modifies files" do |
|
243 |
+ expect { @worker.send(:callback, [file], [], [])}.to change(Event, :count).by(1) |
|
244 |
+ payload = Event.last.payload |
|
245 |
+ expect(payload['event_type']).to eq('modified') |
|
246 |
+ end |
|
247 |
+ |
|
248 |
+ it "creates an event for modifies files" do |
|
249 |
+ expect { @worker.send(:callback, [], [file], [])}.to change(Event, :count).by(1) |
|
250 |
+ payload = Event.last.payload |
|
251 |
+ expect(payload['event_type']).to eq('added') |
|
252 |
+ end |
|
253 |
+ |
|
254 |
+ it "creates an event for modifies files" do |
|
255 |
+ expect { @worker.send(:callback, [], [], [file])}.to change(Event, :count).by(1) |
|
256 |
+ payload = Event.last.payload |
|
257 |
+ expect(payload['event_type']).to eq('removed') |
|
258 |
+ end |
|
259 |
+ |
|
260 |
+ it "creates an event each changed file" do |
|
261 |
+ expect { @worker.send(:callback, [], [file], [file2])}.to change(Event, :count).by(2) |
|
262 |
+ end |
|
263 |
+ end |
|
264 |
+ |
|
265 |
+ context "#listen_options" do |
|
266 |
+ it "returns the path when a directory is given" do |
|
267 |
+ expect(@worker.send(:listen_options)).to eq([File.join(Rails.root, 'tmp', 'spec'), ignore!: []]) |
|
268 |
+ end |
|
269 |
+ |
|
270 |
+ it "restricts to only the specified filename" do |
|
271 |
+ @worker.agent.options['path'] = File.join(Rails.root, 'tmp', 'one') |
|
272 |
+ expect(@worker.send(:listen_options)).to eq([File.join(Rails.root, 'tmp'), { only: /\Aone\z/, ignore!: [] } ]) |
|
273 |
+ end |
|
274 |
+ end |
|
275 |
+ end |
|
276 |
+end |
@@ -0,0 +1,16 @@ |
||
1 |
+require 'rails_helper' |
|
2 |
+ |
|
3 |
+shared_examples_for 'FileHandlingConsumer' do |
|
4 |
+ it 'returns a file pointer' do |
|
5 |
+ expect(@checker.get_file_pointer('testfile')).to eq(file_pointer: { file: "testfile", agent_id: @checker.id}) |
|
6 |
+ end |
|
7 |
+ |
|
8 |
+ it 'get_io raises an exception when trying to access an agent of a different user' do |
|
9 |
+ @checker2 = @checker.dup |
|
10 |
+ @checker2.user = users(:bob) |
|
11 |
+ @checker2.save! |
|
12 |
+ expect(@checker2.user.id).not_to eq(@checker.user.id) |
|
13 |
+ event = Event.new(user: @checker.user, payload: {'file_pointer' => {'file' => 'test', 'agent_id' => @checker2.id}}) |
|
14 |
+ expect { @checker.get_io(event) }.to raise_error(ActiveRecord::RecordNotFound) |
|
15 |
+ end |
|
16 |
+end |
@@ -50,4 +50,28 @@ shared_examples_for WorkingHelpers do |
||
50 | 50 |
expect(@agent.received_event_without_error?).to eq(true) |
51 | 51 |
end |
52 | 52 |
end |
53 |
+ |
|
54 |
+ describe "checked_without_error?" do |
|
55 |
+ before do |
|
56 |
+ @agent = described_class.new |
|
57 |
+ end |
|
58 |
+ |
|
59 |
+ it "should return false until the first time check ran" do |
|
60 |
+ expect(@agent.checked_without_error?).to eq(false) |
|
61 |
+ @agent.last_check_at = Time.now |
|
62 |
+ expect(@agent.checked_without_error?).to eq(true) |
|
63 |
+ end |
|
64 |
+ |
|
65 |
+ it "should return false when the last error occured after the check" do |
|
66 |
+ @agent.last_check_at = Time.now - 1.minute |
|
67 |
+ @agent.last_error_log_at = Time.now |
|
68 |
+ expect(@agent.checked_without_error?).to eq(false) |
|
69 |
+ end |
|
70 |
+ |
|
71 |
+ it "should return true when the last check occured after the last error" do |
|
72 |
+ @agent.last_check_at = Time.now |
|
73 |
+ @agent.last_error_log_at = Time.now - 1.minute |
|
74 |
+ expect(@agent.checked_without_error?).to eq(true) |
|
75 |
+ end |
|
76 |
+ end |
|
53 | 77 |
end |